﻿using Coder.Beer.Robot.Api.ApplicationModel;
using Coder.Beer.Robot.Api.Repository;
using Flurl;
using Flurl.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace Coder.Beer.Robot.Api
{
    public class NodeBBWatcher : BackgroundService
    {
        private static JsonSerializerOptions options = new JsonSerializerOptions()
        {
            PropertyNameCaseInsensitive = true
        };
        private BeerDbContext beerDbContext;
        private readonly IServiceProvider serviceProvider;

        public NodeBBWatcher(IServiceProvider serviceProvider)
        {
            this.serviceProvider = serviceProvider;
        }
        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            return WatchRecentTopicsAsync(stoppingToken);
        }

        private async Task WatchRecentTopicsAsync(CancellationToken stoppingToken)
        {
            while (true)
            {
                try
                {
                    await Environment.GetEnvironmentVariable("MIRAI_NET").AppendPathSegment($"/api/robot/ready").GetStringAsync();
                    break;
                }
                catch (Exception ex)
                {
                    Log.Error(ex, "等待 Mirai 机器人启动");
                    await Task.Delay(10000);
                }
            }
            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    beerDbContext = serviceProvider.CreateScope().ServiceProvider.GetRequiredService<BeerDbContext>();
                    try
                    {
                        var unixTimestamp = (DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds;
                        await SendNewTopicsToGroupAsync();
                        var hasNewRepliesTopics = await SendNewRepliesToUsersAsync(unixTimestamp);
                        await SendNewRepliesToJoinMembersAsync(unixTimestamp, hasNewRepliesTopics);
                        await Task.Delay(5000);
                    }
                    catch (FlurlHttpException ex)
                    {
                        var message = await ex.GetResponseStringAsync();
                        await SendAlertAsync((!string.IsNullOrWhiteSpace(message) ? message : ex.ToString()));
                        await Task.Delay(600000);
                    }
                    catch (Exception ex)
                    {
                        await SendAlertAsync(ex.ToString());
                        Log.Error(ex, "啤酒会机器人异常");
                        await Task.Delay(600000);
                    }
                }
                catch (Exception ex)
                {
                    Log.Error(ex, "啤酒会机器人异常");
                    await Task.Delay(600000);
                }
            }
        }

        private static async Task SendAlertAsync(string message)
        {
            await Environment.GetEnvironmentVariable("MIRAI_NET").AppendPathSegment($"/api/robot/send/1101385391/").PostJsonAsync(new
            {
                message = "社区机器人异常：\n" + message
            });
        }

        private async Task SendNewRepliesToJoinMembersAsync(double unixTimestamp, List<NodeBBTopic> hasNewRepliesTopics)
        {
            var allRepliedWatchedUserIdMap = new Dictionary<string, List<string>>();
            foreach (var topic in hasNewRepliesTopics)
            {
                var topicContent = await $"http://www.coder.beer/api/topic/{topic.TId}?_uid=1".WithHeader("Authorization", "Bearer b0259518-b315-49db-9475-752f7f9bf7b8")
                   .GetStringAsync();
                var fullTopicInfo = JsonSerializer.Deserialize<NodeBBTopic>(topicContent, options);
                var allRepliedUsers = fullTopicInfo.Posts.Where(x => x.Uid != topic.UId).Select(x => x.User.Username).ToHashSet();
                foreach (var username in allRepliedUsers)
                {
                    var watchedTopics = await GetUserWatchedTopicsAsync(username);
                    if (watchedTopics.Topics.Any(x => x.TId == topic.TId))
                    {
                        allRepliedWatchedUserIdMap.TryGetValue(username, out var messages);
                        if (messages == null)
                        {
                            messages = new List<string>();
                            allRepliedWatchedUserIdMap[username] = messages;
                        }
                        messages.Add($"http://www.coder.beer/topic/{topic.TId} {topic.Title}");
                    }
                }
            }

            var allRepliedWatchedUsers = new Dictionary<NodeBBUser, List<string>>();
            foreach (var username in allRepliedWatchedUserIdMap.Keys)
            {
                allRepliedWatchedUsers.Add(await QueryUserAsync(username), allRepliedWatchedUserIdMap[username]);
            }

            var offlineUsers = allRepliedWatchedUsers.Where(x => (unixTimestamp - x.Key.LastOnline) / 1000 / 60 > 5).ToDictionary(x => x.Key.Username, x => x.Value);
            var offlineUsernames = offlineUsers.Keys.ToArray();
            var connectedUsers = beerDbContext.UserConnecteds.Where(x => offlineUsernames.Contains(x.BeerUsername)).ToArray();
            foreach (var item in connectedUsers)
            {
                await SendMessageToUserAsync(item, "你参与的话题有新的回复：\n" + string.Join('\n', offlineUsers[item.BeerUsername]));
            }
        }

        private async Task<List<NodeBBTopic>> SendNewRepliesToUsersAsync(double unixTimestamp)
        {
            var postsContent = await "http://www.coder.beer/api/recent/posts/day?_uid=1".WithHeader("Authorization", "Bearer b0259518-b315-49db-9475-752f7f9bf7b8")
               .GetStringAsync();
            var posts = JsonSerializer.Deserialize<NodeBBPost[]>(postsContent, options);

            var postIds = posts.Select(x => x.PId).ToArray();
            var existPosts = beerDbContext.NodeBBPosts.Where(x => postIds.Contains(x.PId)).ToDictionary(x => x.PId);
            var hasNewPostTopics = new List<NodeBBTopic>();
            var needNotifyUsers = new List<NodeBBUser>();
            foreach (var post in posts)
            {
                existPosts.TryGetValue(post.PId, out var oldPost);
                if (oldPost == null)
                {
                    if (!hasNewPostTopics.Any(x => x.TId == post.Topic.TId))
                    {
                        hasNewPostTopics.Add(post.Topic);
                    }
                    if (!needNotifyUsers.Any(x => x.UId == post.Topic.UId))
                    {
                        needNotifyUsers.Add(await QueryUserAsync(post.Topic.UId));
                    }
                    post.Topic = null;
                    post.User = null;
                    beerDbContext.NodeBBPosts.Add(post);
                }

            }
            if (needNotifyUsers.Any())
            {
                Log.Information("需要通知：" + needNotifyUsers.Count + "人");
                var offlineUsers = needNotifyUsers.Where(x => (unixTimestamp - x.LastOnline) / 1000 / 60 > 5).ToDictionary(x => x.Username);
                Log.Information("在线：" + needNotifyUsers.Count + "人");
                var offlineUsernames = offlineUsers.Values.Select(x => x.Username).ToArray();
                var connectedUsers = beerDbContext.UserConnecteds.Where(x => offlineUsernames.Contains(x.BeerUsername)).ToArray();
                Log.Information("已绑定：" + needNotifyUsers.Count + "人");
                foreach (var connectedUser in connectedUsers)
                {
                    try
                    {
                        var watchedTopics = await GetUserWatchedTopicsAsync(connectedUser.BeerUsername);

                        var links = hasNewPostTopics.Where(x => watchedTopics.Topics.Any(y => y.TId == x.TId)).Select(x => $"http://www.coder.beer/topic/{x.TId} {x.Title}").ToArray();
                        if (!links.Any())
                        {
                            Log.Information("没有关注：" + connectedUser.BeerUsername);
                            continue;
                        }
                        await SendMessageToUserAsync(connectedUser, "社区提问有新的回复：\n" + string.Join(Environment.NewLine, links));
                    }
                    catch (Exception ex)
                    {
                        await SendAlertAsync(ex.ToString());
                        Log.Error("发送消息失败", ex);
                    }
                }

                await beerDbContext.SaveChangesAsync();
            }
            return hasNewPostTopics;
        }

        private static async Task SendMessageToUserAsync(Repository.Model.UserConnected connectedUser, string message)
        {
            await Environment.GetEnvironmentVariable("MIRAI_NET").AppendPathSegment($"/api/robot/send/{connectedUser.QQ}").PostJsonAsync(new
            {
                message = message + "\n取消通知：进入对应的主题贴页面将关注改为不关注或忽略"
            });
        }

        public static async Task SendMessageToUserAsync(string qq, string message)
        {
            await Environment.GetEnvironmentVariable("MIRAI_NET").AppendPathSegment($"/api/robot/send/{qq}").PostJsonAsync(new
            {
                message = message
            });
        }

        private static async Task<UserWatchdTopicResponse> GetUserWatchedTopicsAsync(string username)
        {
            var watchedTopicsContent = await $"http://www.coder.beer/api/user/{username}/watched".WithHeader("Authorization", "Bearer b0259518-b315-49db-9475-752f7f9bf7b8")
        .GetStringAsync();
            var watchedTopics = JsonSerializer.Deserialize<UserWatchdTopicResponse>(watchedTopicsContent, options);
            return watchedTopics;
        }

        private async Task SendNewTopicsToGroupAsync()
        {
            var topicsContent = await "http://www.coder.beer/api/recent?_uid=1".WithHeader("Authorization", "Bearer b0259518-b315-49db-9475-752f7f9bf7b8")
                                   .GetStringAsync();
            var topics = JsonSerializer.Deserialize<NodeBBRecentTopicResponse>(topicsContent, options);
            var topicIds = topics.Topics.Select(x => x.TId).ToArray();
            Log.Information("最近主题：" + string.Join(",", topicIds));
            var existTopics = beerDbContext.NodeBBTopics.Where(x => topicIds.Contains(x.TId)).ToDictionary(x => x.TId);
            var newTopics = new List<NodeBBTopic>();
            foreach (var topic in topics.Topics)
            {
                existTopics.TryGetValue(topic.TId, out var oldTopic);
                if (oldTopic == null)
                {
                    beerDbContext.NodeBBTopics.Add(topic);
                    newTopics.Add(topic);
                    continue;
                }
            }

            if (newTopics.Any())
            {
                Log.Information("本次新发现主题：" + string.Join(",", newTopics.Select(x => x.TId)));
                var links = newTopics.Select(x => $"http://www.coder.beer/topic/{x.TId} {x.Title}").ToArray();
                await Environment.GetEnvironmentVariable("MIRAI_NET").AppendPathSegment($"/api/robot/sendGroup/749042893/").PostJsonAsync(new
                {
                    message = "社区有新的提问：\n" + string.Join(Environment.NewLine, links)
                });
                await beerDbContext.SaveChangesAsync();
            }

        }

        private async Task<NodeBBUser> QueryUserAsync(int uId)
        {
            var userContent = await $"http://www.coder.beer/api/user/uid/{uId}".WithHeader("Authorization", "Bearer b0259518-b315-49db-9475-752f7f9bf7b8")
               .GetStringAsync();
            return JsonSerializer.Deserialize<NodeBBUser>(userContent, options);
        }

        private async Task<NodeBBUser> QueryUserAsync(string username)
        {
            var userContent = await $"http://www.coder.beer/api/user/username/{username}".WithHeader("Authorization", "Bearer b0259518-b315-49db-9475-752f7f9bf7b8")
               .GetStringAsync();
            return JsonSerializer.Deserialize<NodeBBUser>(userContent, options);
        }
    }
}
